<?php

namespace app\service;

use app\service\tool\ConvertService;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\RequestOptions;
use linslin\yii2\curl\Curl;

/**
 * 发送远程请求
 * User: Administrator
 * Date: 2018/3/29
 * Time: 10:05
 * @property $url
 * @property $method
 * @property $params
 * @property $header
 * @property $cookie
 * @property $paramsPos
 * @property $timeOutSecond
 */
final class RequestService extends BaseService
{

    /**
     * 实例本身
     * @var self
     */
    protected static $instance;
    /**
     * 请求类型
     * @var array
     */
    private static $requestType = [
        'GET'  => 'GET',
        'POST' => 'POST',
        'XML'  => 'XML',
        'JSON' => 'JSON'
    ];
    /**
     * 参数位置列表
     * @var array
     */
    private static $paramsPosList = [
        'get' => RequestOptions::QUERY,
        'post' => RequestOptions::FORM_PARAMS,
        'body' => RequestOptions::BODY,
        'json' => RequestOptions::JSON
    ];

    /**
     * 超时时间
     * @var int
     */
    protected $timeOutSecond = 10;
    /**
     * 存储结果
     * @var array|string
     */
    protected $result = [];

    /**
     * 返回[COOKIE]
     * @var null
     */
    protected $resCookie = null;

    /**
     * [header]预设
     * @var array
     */
    public static $headerSetting = [
        'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8',
        'UserLoginModel-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0'
        // 'Content-Length' => 9999999
    ];

    /**
     * 验证规则
     * @return array
     */
    public function rules()
    {

        // 请求类型列表
        $methodList = array_values(self::getMethodList());
        // 参数位置列表
        $paramPosList = self::getParamsPosList();

        return [
            [['url', 'method'], 'required'],
            ['timeOutSecond', 'integer'],
            ['method', 'in', 'range' => $methodList],
            ['url', 'url'],
            [['params', 'header', 'cookie'], 'safe'],
            ['paramsPos', 'in', 'range' => $paramPosList]
        ];
    }

    public function attributeLabels()
    {
        return [
            'requestType' => '请求类型预设',
            'method'      => '请求类型',
            'url'         => '请求链接'
        ];
    }

    /**
     * 加载整体[Model]
     * @param array $data 初始化的数据
     * @return self
     */
    public static function loadModel($data = [])
    {

        return self::getInstance()->initInstance($data);
    }

    /**
     * 获取实例
     * @return self
     */
    protected static function getInstance()
    {

        // 实例不存在初始化
        if (empty(self::$instance)) self::$instance = new self();

        // 存在直接返回
        return self::$instance;
    }

    /**
     * 初始化一些默认信息
     * @param array $_data
     * @return self
     */
    protected function initInstance($_data = [])
    {

        // 获取实例
        $instance = self::getInstance();

        ### 做一些参数的赋值
        // 请求类型
        $methodList = array_values(self::getMethodList());
        if (empty($_data['method']) || !in_array($_data['method'], $methodList)) {
            $_data['method'] = self::getMethodGet();
        }

        // 参数赋值
        if (!empty($_data)) $instance->setAttributes($_data);

        // 返回
        return $instance;
    }

    /**
     * 发送请求
     * @return bool
     */
    public function doRequest2()
    {

        try {

            // ☆☆☆ 某些设置 ☆☆☆
            // 请求方式
            $method = $this->method;
            // 请求参数
            $params = $this->params;

            // ☆☆☆ 初始化类 ☆☆☆
            // 实例化curl
            $curl = new Curl();
            // 添加的头部
            if (!empty($this->header)) $curl->setHeaders($this->header);
            // 添加的Cookie
            if (!empty($this->cookie)) $curl->setOption(CURLOPT_COOKIE, $this->cookie);

            // ☆☆☆ 执行请求 ☆☆☆
            $response = null;
            // 判断是[GET]
            if (strtoupper($method) == self::getMethodGet()) {

                $response = $curl
                    ->setGetParams($params)
                    ->get($this->url);
            } // 判断是[POST]
            else if (strtoupper($method) == self::getMethodPost()) {

                $response = $curl
                    ->setPostParams($params)
                    ->post($this->url);
            } // 判断是[XML]
            else if (strtoupper($method) == self::getMethodXML()) {

                $paramsXml = "<xml>";
                $paramsXml .= $this->arrayToXml($params);
                $paramsXml .= "</xml>";
                $response  = $curl
                    ->setRawPostData($paramsXml)
                    ->post($this->url);
            } // 判断是[JSON]
            else if (strtoupper($method) == self::getMethodJSON()) {

                $params   = !$params ? null : json_encode($params, JSON_UNESCAPED_UNICODE);
                $response = $curl
                    ->setRequestBody($params)
                    ->get($this->url);
            }

            if (!empty($this->header)) {
                var_dump(123, $curl);
                die;
            }
            // ☆☆☆ 返回结果 ☆☆☆
            // ERROR CODE 错误码错误
            if (!empty($curl->errorCode) || !empty($curl->errorText)) {
                $this->addError($curl->errorCode, $curl->errorText);
                return false;
            }
            // 响应码错误
            if ($curl->responseCode != 200) {
                // 返回 false 就是有错误
                $this->addError('code error',
                    $this->getErrMsgForChinese($curl->responseCode));
                return false;
            }

            // 最终成功 - 暂存结果
            $this->result = $response;
            // 最终成功 - 暂存返回COOKIE
            $this->resCookie = empty($curl->responseHeaders['Set-Cookie']) ?
                null : $curl->responseHeaders['Set-Cookie'];

            return true;

        } catch (\Exception $exception) {

            // 记录下错误日志
            \Yii::error([
                "************************* 请求发送类错误 *************************",
                "错误详情：请求发送失败",
                $exception->getMessage(),
                "错误详情: ",
                $exception->getTraceAsString()
            ], 'error');

            $this->addError('500', '请求发送失败：' . $exception->getMessage());
            return false;
        }
    }

    /**
     * 发送请求
     * @param bool $jsonToArr JSON字符串转数组
     * @return bool
     */
    public function doRequest($jsonToArr = false)
    {

        // 先做验证
        if ($this->hasErrors() || !$this->validate()) return false;

        try {

            // ☆☆☆ 初始化类 GuzzleHttp ☆☆☆
            // 实例化curl
            $client = new Client();
            // 请求参数 位置
            if (!isset($this->paramsPos)) {
                $this->paramsPos = $this->method == self::getMethodGet() ?
                    RequestOptions::QUERY : RequestOptions::FORM_PARAMS;
            }

            $result = $client->request($this->method, $this->url, [
                RequestOptions::TIMEOUT => $this->timeOutSecond,
                RequestOptions::HEADERS => $this->header,
                $this->paramsPos        => $this->params
            ]);

            // ☆☆☆ 返回结果 ☆☆☆
            // 响应码错误
            if ($result->getStatusCode() != 200) {
                // 返回 false 就是有错误
                $this->addError('返回状态码错误',
                    $this->getErrMsgForChinese($result->getStatusCode()));
                return false;
            }

            // 最终成功 - 暂存结果
            $this->result = $result->getBody()->getContents();
            // 需要强制转数组
            if ($jsonToArr) $this->result = json_decode($this->result, true);
            // 最终成功 - 暂存返回COOKIE
            $this->resCookie = $result->getHeader('Set-Cookie');

            return true;

        } catch (GuzzleException $exception) {

            // 记录下错误日志
            \Yii::error([
                "************************* 请求发送类错误 *************************",
                "错误详情：请求发送失败",
                $exception->getMessage(),
                "错误详情: ",
                $exception->getTraceAsString()
            ], 'error');

            $this->addError('500', '请求发送失败：' . $exception->getMessage());
            return false;
        }
    }

    /**
     * 获取请求结果
     * @return array|string
     */
    public function getResult()
    {

        // 如果走的是XML返回转换下
        if (strtolower($this->method) == 'xml') {
            $result = $this->FromXml($this->result);
        } else if (ConvertService::isJson($this->result)) {
            $result = json_decode($this->result, true);
        } else {
            $result = $this->result;
        }

        return $result;
    }

    /**
     * 获取Cookie
     * @param bool $key
     * @param bool $returnString
     * @return array|mixed|null
     */
    public function getCookie($key = false, $returnString = false)
    {

        // 空的返回下
        if (empty($this->resCookie)) return null;
        // 直接返回字符串
        if ($returnString) return $this->resCookie;

        $cookie = $this->resCookie;
        // Cookie 先替换下
        $cookie = preg_replace('/\;\s*/', ';', $cookie);

        // Cookie 分割
        $cookie    = explode(';', $cookie[0]);
        $endCookie = [];
        array_walk($cookie, function ($data, $key) use (&$endCookie) {

            // = 分割，最终赋值[$endCookie]组合为数组 key => value 形势
            $stage = explode('=', $data);
            if (empty($stage[0]) || empty($stage[1])) return false;
            $endCookie[$stage[0]] = $stage[1];
            return $endCookie;
        });

        // 有需要返回的键值
        if ($key && !empty($endCookie[$key])) return $endCookie[$key];

        // 否则全返回
        return $endCookie;
    }

    /**
     * 数组xml字符
     * @param array $params
     * @return string
     */
    protected function arrayToXml($params = [])
    {

        if (empty($params))

            return '';

        $xml = "";
        foreach ($params as $key => $val) {

            if (is_array($val)) {
                $xml .= $this->arrayToXml($val);
                continue;
            }
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }

        return $xml;
    }

    /**
     * xml转为array
     * @param $xml
     * @return mixed|string
     */
    public function FromXml($xml)
    {

        if (!$xml) {
            return '';
        }
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $values;
    }

    /**
     * 获取错误中文
     * @param $code
     * @return mixed|string
     */
    private function getErrMsgForChinese($code)
    {
        $data = [
            '400' => '错误请求 — 请求中有语法问题，或不能满足请求。',
            '401' => '未授权 — 未授权客户机访问数据。',
            '402' => '需要付款 — 表示计费系统已有效。',
            '403' => '禁止 — 即使有授权也不需要访问。',
            '404' => '找不到 — 服务器找不到给定的资源；文档不存在。',
            '405' => '资源被禁止',
            '406' => '无法接受 ',
            '407' => '代理认证请求 — 客户机首先必须使用代理认证自身。',
            '410' => '永远不可用',
            '412' => '先决条件失败',
            '414' => '请求URI太长',
            '415' => '介质类型不受支持 — 服务器拒绝服务请求，因为不支持请求实体的格式。',
            '500' => '内部错误 — 因为意外情况，服务器不能完成请求。',
            '501' => '未执行 — 服务器不支持请求的工具。',
            '502' => '错误网关 — 服务器接收到来自上游服务器的无效响应。',
            '503' => '无法获得服务 — 由于临时过载或维护，服务器无法处理请求。',

        ];

        if (empty($data[$code])) {
            return '未知错误';
        }

        return $data[$code];
    }

    /**
     * 请求类型[GET]值
     * @return mixed
     */
    public static function getMethodGet()
    {

        return self::$requestType['GET'];
    }

    /**
     * 请求类型[POST]值
     * @return mixed
     */
    public static function getMethodPost()
    {

        return self::$requestType['POST'];
    }

    /**
     * 请求类型[XML]值
     * @return mixed
     */
    public static function getMethodXML()
    {

        return self::$requestType['XML'];
    }

    /**
     * 请求类型[JSON]值
     */
    public static function getMethodJSON()
    {

        return self::$requestType['JSON'];
    }

    /**
     * 请求类型[全部]值
     * @return mixed
     */
    public static function getMethodList()
    {

        return self::$requestType;
    }

    /**
     * 请求参数地址位置[GET 请求]值
     * @return mixed
     */
    public static function getParamsPosGet()
    {

        return self::$paramsPosList['get'];
    }

    /**
     * 请求参数地址位置[POST 请求]值
     * @return mixed
     */
    public static function getParamsPosPost()
    {

        return self::$paramsPosList['post'];
    }

    /**
     * 请求参数地址位置[BODY 请求]值
     * @return mixed
     */
    public static function getParamsPosBody()
    {

        return self::$paramsPosList['body'];
    }

    /**
     * 请求参数地址位置[JSON 请求]值
     * @return mixed
     */
    public static function getParamsPosJson()
    {

        return self::$paramsPosList['json'];
    }

    /**
     * 请求参数地址位置[全部]值
     * @return mixed
     */
    public static function getParamsPosList()
    {

        return self::$paramsPosList;
    }
}
